// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Data;
using IBM.Data.DB2.Core;
using SnapObjects.Commons;

namespace SnapObjects.Data.Informix
{
    public class InformixTypeMapper : TypeMapper
    {
        #region String Type Mapping
        private readonly InformixStringTypeMapping _unboundedUnicodeString
            = new InformixStringTypeMapping("nvarchar(max)", db2Type: DB2Type.NVarChar, unicode: true);

        private readonly InformixStringTypeMapping _keyUnicodeString
            = new InformixStringTypeMapping("nvarchar(450)", db2Type: DB2Type.NVarChar, unicode: true, size: 450);

        private readonly InformixStringTypeMapping _unboundedAnsiString
            = new InformixStringTypeMapping("varchar(max)", db2Type: DB2Type.VarChar);

        private readonly InformixStringTypeMapping _keyAnsiString
            = new InformixStringTypeMapping("varchar(900)", db2Type: DB2Type.VarChar, unicode: false, size: 900);

        private readonly InformixStringTypeMapping _char
            = new InformixStringTypeMapping("char", db2Type: DB2Type.Char);

        private readonly InformixStringTypeMapping _nchar
            = new InformixStringTypeMapping("nchar", db2Type: DB2Type.NChar, unicode: true);

        private readonly InformixStringTypeMapping _varchar
            = new InformixStringTypeMapping("varchar", db2Type: DB2Type.VarChar);

        private readonly InformixStringTypeMapping _nvarchar
            = new InformixStringTypeMapping("nvarchar", db2Type: DB2Type.NVarChar, unicode: true);

        private readonly InformixStringTypeMapping _characterVarying
            = new InformixStringTypeMapping("character varying", db2Type: DB2Type.VarChar);

        private readonly InformixStringTypeMapping _lvarchar
            = new InformixStringTypeMapping("lvarchar", db2Type: DB2Type.LongVarChar);

        private readonly InformixStringTypeMapping _clob
            = new InformixStringTypeMapping("clob", db2Type: DB2Type.Clob);
        #endregion

        #region long Type Mapping
        private readonly InformixLongTypeMapping _bigserial = new InformixLongTypeMapping("bigserial", DB2Type.BigSerial);
        private readonly InformixLongTypeMapping _bigint = new InformixLongTypeMapping("bigint", DB2Type.BigInt);
        #endregion

        #region Binary Type Mapping
        private readonly InformixByteArrayTypeMapping _unboundedBinary
            = new InformixByteArrayTypeMapping("blob", DB2Type.Blob);

        private readonly InformixByteArrayTypeMapping _keyBinary
            = new InformixByteArrayTypeMapping("byte", DB2Type.Blob);

        private readonly InformixByteArrayTypeMapping _byte
            = new InformixByteArrayTypeMapping("byte", DB2Type.Blob);

        private readonly InformixByteArrayTypeMapping _blob
            = new InformixByteArrayTypeMapping("blob", DB2Type.Blob);
        #endregion

        #region Short Type Mapping
        private readonly InformixShortTypeMapping _smallint = new InformixShortTypeMapping("smallint", DB2Type.SmallInt);
        private readonly BoolTypeMapping _boolean = new InformixBoolTypeMapping();
        #endregion

        #region Integer Type mapping
        private readonly InformixIntTypeMapping _int = new InformixIntTypeMapping("integer");
        private readonly InformixIntTypeMapping _serial = new InformixIntTypeMapping("serial", db2Type: DB2Type.Serial);
        #endregion

        #region Date(Time) Type Mapping
        private readonly InformixDateTimeTypeMapping _date = new InformixDateTimeTypeMapping("date", db2Type: DB2Type.Date, dbType: DbType.Date);
        private readonly InformixDateTimeTypeMapping _datetime = new InformixDateTimeTypeMapping("datetime year to fraction", db2Type: DB2Type.DateTime, dbType: DbType.DateTime);
        private readonly InformixTimeSpanTypeMapping _time = new InformixTimeSpanTypeMapping("time", dbType: DbType.Time);
        #endregion

        #region Double Decimal Float Type Mapping
        private readonly DoubleTypeMapping _double = new InformixDoubleTypeMapping("double", dbType: DbType.Double);

        private readonly FloatTypeMapping _real = new InformixFloatTypeMapping("real", db2Type: DB2Type.Real, DbType.Single);

        private readonly InformixDecimalTypeMapping _decimal = new InformixDecimalTypeMapping("decimal");
        #endregion


        private readonly Dictionary<string, TypeMapping> _dbTypeMappings;
        private readonly Dictionary<Type, TypeMapping> _clrTypeMappings;
        private readonly HashSet<string> _disallowedMappings;

        public InformixTypeMapper(IDataContextOptions options) : base(options)
        {
            _dbTypeMappings
                = new Dictionary<string, TypeMapping>(StringComparer.OrdinalIgnoreCase)
                {
                    {"serial", _serial },
                    {"bigserial", _bigserial },
                    {"serial8", _bigserial },
                    {"bigint", _bigint },
                    {"int8", _bigint },
                    {"integer", _int },

                    {"decimal", _decimal },
                    {"money", _decimal },
                    {"numeric", _decimal },

                    {"double", _double },
                    {"double precision", _double },
                    {"float", _double },

                    {"real", _real },
                    {"smallfloat", _real },

                    {"smallint", _smallint },
                    {"boolean", _boolean },

                    {"blob", _blob },
                    {"byte", _byte },

                    #region string char text
                    {"character varying", _characterVarying },
                    {"varchar", _varchar },
                    {"nvarchar", _nvarchar },
                    {"char", _char },
                    {"nchar", _nchar },
                    {"clob", _clob },
                    {"text", _clob },
                    {"lvarchar", _lvarchar },
                    #endregion

                    #region date(time)
                    {"date", _date },
                    {"datetime", _datetime},
                    {"datetime year to year", _date },
                    {"datetime year to month", _date },
                    {"datetime year to day", _date },
                    {"dateime year to hour", _datetime },
                    {"datetime year to minute", _datetime },
                    {"datetime year to second", _datetime },
                    {"datetime year to fraction", _datetime },
                    {"datetime month to month", _date },
                    {"datetime month to day", _date },
                    {"datetime month to hour", _datetime },
                    {"datetime month to minute", _datetime },
                    {"datetime month to second", _datetime },
                    {"datetime month to fraction", _datetime },
                    {"datetime day to day", _date },
                    {"datetime day to hour", _datetime },
                    {"datetime day to minute", _datetime },
                    {"datetime day to second", _datetime },
                    {"datetime day to fraction", _datetime },
                    {"time", _time },
                    {"datetime hour to hour", _time },
                    {"datetime hour to minute", _time },
                    {"datetime hour to second", _time },
                    {"datetime hour to fraction", _datetime },
                    {"datetime minute to minute", _time },
                    {"datetime minute to second", _time },
                    {"datetime minute to fraction", _datetime },
                    {"datetime second to second", _time },
                    {"datetime second to fraction", _datetime },
                    {"datetime fraction to fraction", _datetime },
                    #endregion

                    #region interval
                    {"interval year to year", _varchar },
                    {"interval year to month", _varchar },
                    {"interval month to month", _varchar },
                    {"interval day to day", _varchar },
                    {"interval day to hour", _varchar },
                    {"interval day to minute", _varchar },
                    {"interval day to second", _varchar },
                    {"interval day to fraction", _varchar },
                    {"interval hour to hour", _varchar },
                    {"interval hour to minute", _varchar },
                    {"interval hour to second", _varchar },
                    {"interval hour to fraction", _varchar },
                    {"interval minute to minute", _varchar },
                    {"interval minute to second", _varchar },
                    {"interval minute to fraction", _varchar },
                    {"interval second to second", _varchar },
                    {"interval second to fraction", _varchar },
                    {"interval fraction to fraction", _varchar },
                    #endregion

               

                };

            // Note: sbyte, ushort, uint, char and ulong type mappings are not supported by SQL Server.
            // We would need the type conversions feature to allow this to work - see https://github.com/aspnet/EntityFramework/issues/242.
            _clrTypeMappings
                = new Dictionary<Type, TypeMapping>
                {
                    {typeof(byte[]), _blob },
                    { typeof(DateTime), _datetime },
                    { typeof(decimal), _decimal },
                    { typeof(double), _double },
                    { typeof(short), _smallint },
                    { typeof(int), _int },
                    { typeof(long), _bigint },
                    { typeof(string), _varchar},
                    { typeof(TimeSpan), _time},
                    { typeof(float), _real},
                    { typeof(bool), _boolean}
                };

            // These are disallowed only if specified without any kind of length specified in parenthesis.
            // This is because we don't try to make a new type from this string and any max length value
            // specified in the model, which means use of these strings is almost certainly an error, and
            // if it is not an error, then using, for example, varbinary(1) will work instead.
            _disallowedMappings
                = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
                {
                    /*"binary varying",
                    "binary",
                    "char varying",
                    "char",
                    "character varying",
                    "character",
                    "national char varying",
                    "national character varying",
                    "national character",
                    "nchar",
                    "nvarchar",
                    "varbinary",
                    "varchar"*/
                };

            this.ByteArrayMapper
                = new ByteArrayTypeMapper(
                    maxBoundedLength: 8000,
                    defaultMapping: _unboundedBinary,
                    unboundedMapping: _unboundedBinary,
                    keyMapping: _keyBinary,
                    createBoundedMapping: size => new InformixByteArrayTypeMapping(
                        "blob",
                        DB2Type.Blob,
                        DbType.Binary));

            this.StringMapper
                = new StringTypeMapper(
                    maxBoundedAnsiLength: 8000,
                    defaultAnsiMapping: _unboundedAnsiString,
                    unboundedAnsiMapping: _unboundedAnsiString,
                    keyAnsiMapping: _keyAnsiString,
                    createBoundedAnsiMapping: size => new InformixStringTypeMapping(
                        "varchar(" + size + ")",
                        unicode: false,
                        size: size),
                    maxBoundedUnicodeLength: 4000,
                    defaultUnicodeMapping: _unboundedUnicodeString,
                    unboundedUnicodeMapping: _unboundedUnicodeString,
                    keyUnicodeMapping: _keyUnicodeString,
                    createBoundedUnicodeMapping: size => new InformixStringTypeMapping(
                        "nvarchar(" + size + ")",
                        db2Type: null,
                        unicode: true,
                        size: size));
        }

        public override IByteArrayTypeMapper ByteArrayMapper { get; }

        public override IStringTypeMapper StringMapper { get; }

        public override void ValidateTypeName(string dbTypeName)
        {
            if (_disallowedMappings.Contains(dbTypeName))
            {
                throw new ArgumentException(InformixStrings.UnqualifiedDataType(dbTypeName));
            }
        }

        protected override IReadOnlyDictionary<Type, TypeMapping> GetClrTypeMappings()
        {
            return _clrTypeMappings;
        }

        protected override IReadOnlyDictionary<string, TypeMapping> GetDbTypeMappings()
        {
            return _dbTypeMappings;
        }
    }
}
